/*******************************************************************************
 * Copyright (c) 2007, 2010 Intel Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.settings.model;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.settings.model.CExternalSetting;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.extension.CExternalSettingProvider;
import org.eclipse.cdt.internal.core.settings.model.CExternalSettingsManager.CContainerRef;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;

/**
 * Responsible for managing external settings providers contributed through the extension point
 */
public class ExtensionContainerFactory extends CExternalSettingContainerFactoryWithListener {
	static final String FACTORY_ID = CCorePlugin.PLUGIN_ID + ".extension.container.factory"; //$NON-NLS-1$
	private static final String EXTENSION_ID = CCorePlugin.PLUGIN_ID + ".externalSettingsProvider"; //$NON-NLS-1$
	
	private static ExtensionContainerFactory fInstance;
	private Map<String, CExtensionSettingProviderDescriptor> fDescriptorMap;

	private static class NullProvider extends CExternalSettingProvider {
		private static final NullProvider INSTANCE = new NullProvider();
		
		@Override
		public CExternalSetting[] getSettings(IProject project, ICConfigurationDescription cfg) {
			return new CExternalSetting[0];
		}
		
	}
	
	private static class CESContainer extends CExternalSettingsContainer {
		private CExternalSetting[] fSettings;

		CESContainer(CExternalSetting[] settings){
			fSettings = settings.clone();
		}
		
		@Override
		public CExternalSetting[] getExternalSettings() {
			return fSettings.clone();
		}
		
	}
	
	private static class CExtensionSettingProviderDescriptor {
		private static final String PROVIDER = "provider"; //$NON-NLS-1$
		private static final String CLASS = "class"; //$NON-NLS-1$
		
		private IExtension fExtension;
		private IConfigurationElement fProviderElement;
		private String fId;
		private String fName;
		private CExternalSettingProvider fProvider;
		
		CExtensionSettingProviderDescriptor(IExtension extension){
			fId = extension.getUniqueIdentifier();
			fName = extension.getLabel();
			fExtension = extension;
		}
		
		public String getId(){
			return fId;
		}

		@SuppressWarnings("unused")
		public String getName(){
			return fName;
		}

		private CExternalSettingProvider getProvider(){
			if(fProvider == null){
				try {
					fProvider = createProvider();
				} catch (CoreException e) {
					CCorePlugin.log(e);
				}
				if(fProvider == null){
					fProvider = NullProvider.INSTANCE;
				}
			}
			return fProvider;
		}
		
		CExternalSettingsContainer getContainer(IProject project, ICConfigurationDescription cfg, CExternalSetting[] previousSettings){
			return new CESContainer(getProvider().getSettings(project, cfg, previousSettings));
		}

		CExternalSettingProvider createProvider() throws CoreException{
			IConfigurationElement el = getProviderElement();
			if(el != null){
				Object obj = el.createExecutableExtension(CLASS);
				if(obj instanceof CExternalSettingProvider){
					return (CExternalSettingProvider)obj;
				} else
					throw ExceptionFactory.createCoreException(SettingsModelMessages.getString("ExtensionContainerFactory.4")); //$NON-NLS-1$
			}
			throw ExceptionFactory.createCoreException(SettingsModelMessages.getString("ExtensionContainerFactory.5")); //$NON-NLS-1$
		}
		
		private IConfigurationElement getProviderElement(){
			if(fProviderElement == null)
				fProviderElement = getProviderElement(fExtension);
			return fProviderElement;
		}
		
		private static IConfigurationElement getProviderElement(IExtension ext){
			IConfigurationElement els[] = ext.getConfigurationElements();
			for (IConfigurationElement el : els) {
				String name = el.getName();
				if(PROVIDER.equals(name))
					return el;
			}					
			return null;
		}
	}
	
	private Map<String, CExtensionSettingProviderDescriptor> getProviderDescriptorMap(){
		if(fDescriptorMap == null){
			initProviderInfoSynch();
		}
		return fDescriptorMap;
	}
	
	private synchronized void initProviderInfoSynch(){
		if(fDescriptorMap != null)
			return;
		
		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(EXTENSION_ID);
		IExtension exts[] = extensionPoint.getExtensions();
		fDescriptorMap = new HashMap<String, CExtensionSettingProviderDescriptor>();
		
		for (IExtension ext : exts) {
			CExtensionSettingProviderDescriptor dr = new CExtensionSettingProviderDescriptor(ext);
			fDescriptorMap.put(dr.getId(), dr);
		}
	}

	private ExtensionContainerFactory(){
	}
	
	public static ExtensionContainerFactory getInstance(){
		if(fInstance == null){
			fInstance = new ExtensionContainerFactory();
		}
		return fInstance;
	}
	
	public static ExtensionContainerFactory getInstanceInitialized(){
		CExternalSettingContainerFactory f = CExternalSettingsManager.getInstance().getFactory(FACTORY_ID);
		if(f instanceof ExtensionContainerFactory)
			return (ExtensionContainerFactory)f;
		return getInstance();
	}

	@Override
	public CExternalSettingsContainer createContainer(String id,
			IProject project, ICConfigurationDescription cfgDes, CExternalSetting[] previousSettings) throws CoreException {
		CExtensionSettingProviderDescriptor dr = getProviderDescriptorMap().get(id);
		if(dr != null)
			return dr.getContainer(project, cfgDes, previousSettings);
		// Notify the manager that there's no external settings manager matching id.
		throw new CoreException(CCorePlugin.createStatus("External settings provider: \"" + id + "\" couldn't be found for " +  //$NON-NLS-1$//$NON-NLS-2$
			cfgDes.getProjectDescription().getProject() + ":"+cfgDes.getName())); //$NON-NLS-1$
	}
	
	public static String[] getReferencedProviderIds(ICConfigurationDescription cfg){
		CContainerRef[] refs = CExternalSettingsManager.getInstance().getReferences(cfg, FACTORY_ID);
		String[] ids = new String[refs.length];
		for(int i = 0; i < refs.length; i++){
			ids[i] = refs[i].getContainerId();
		}
		return ids;
	}
	
	public static void setReferencedProviderIds(ICConfigurationDescription cfg, String ids[]){
		Set<String> newIdsSet = new HashSet<String>(Arrays.asList(ids));
		Set<String> oldIdsSet = new HashSet<String>(Arrays.asList(getReferencedProviderIds(cfg)));
		Set<String> newIdsSetCopy = new HashSet<String>(newIdsSet);
		newIdsSet.removeAll(oldIdsSet);
		oldIdsSet.removeAll(newIdsSetCopy);
		
		if(oldIdsSet.size() != 0){
			for (String string : oldIdsSet) {
				removeReference(cfg, string);
			}
		}

		if(newIdsSet.size() != 0){
			for (String string : newIdsSet) {
				createReference(cfg, string);
			}
		}
	}

	public static void updateReferencedProviderIds(String ids[], IProgressMonitor monitor){
		ExtensionContainerFactory instance = getInstanceInitialized();
		CExternalSettingsContainerChangeInfo[] changeInfos = 
			new CExternalSettingsContainerChangeInfo[ids.length];
		
		for(int i = 0; i < changeInfos.length; i++){
			changeInfos[i] = new CExternalSettingsContainerChangeInfo(
					CExternalSettingsContainerChangeInfo.CONTAINER_CONTENTS,
					new CContainerRef(FACTORY_ID, ids[i]),
					null);
		}

		instance.notifySettingsChange(null, null, changeInfos);
		
		if(monitor != null)
			monitor.done();
	}
	
	public static void updateReferencedProviderIds(ICConfigurationDescription cfg, String ids[]){
		Set<String> newIdsSet = new HashSet<String>(Arrays.asList(ids));
		Set<String> oldIdsSet = new HashSet<String>(Arrays.asList(getReferencedProviderIds(cfg)));
		Set<String> newIdsSetCopy = new HashSet<String>(newIdsSet);
		newIdsSetCopy.removeAll(oldIdsSet);
		newIdsSet.removeAll(newIdsSetCopy);
		
		if(newIdsSet.size() != 0){
			for (String string : newIdsSet) {
				providerChanged(cfg, string);
			}
		}
	}
	
	private static void createReference(ICConfigurationDescription cfg, String id){
		CContainerRef cr = createContainerRef(id);
		CExternalSettingsManager.getInstance().addContainer(cfg, cr);
	}
	
	private static void providerChanged(ICConfigurationDescription cfg, String id){
		CContainerRef cr = createContainerRef(id);
		CExternalSettingsManager.getInstance().containerContentsChanged(cfg, cr);
		
	}

	private static void removeReference(ICConfigurationDescription cfg, String id){
		CContainerRef cr = createContainerRef(id);
		CExternalSettingsManager.getInstance().removeContainer(cfg, cr);
	}

	private static CContainerRef createContainerRef(String id){
		return new CContainerRef(FACTORY_ID, id);
	}
}
